package com.boshanam.model.entities.persistant.jpa.user;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;

import com.boshanam.model.vos.domain.user.GrantedAuthorityImplVO;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
 * 
 * Models core user information retieved by an {@link UserDetailsService}.
 * <p>
 * Implemented with value object semantics (immutable after construction, like a
 * <code>String</code>). Developers may use this class directly, subclass it.
 * Stores GrantedAuthority objects as Set of Strings and avoids duplicate role names.
 * </p>
 * 
 * @author Sivakumar Y User.java Dec 12, 2009 11:47:04 PM www.ibigbang.net
 * 
 */
@Entity
public class User implements UserDetails {
	// ~ Instance fields ======================================================



	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Key key;

	private String password;
	private String username = null;	

	private String firstName;
	private String lastName;
	private String middleName;
	private String phoneNumber;
	
	private Date birthDate;
	
	private String email = null;
	
	//@Basic(fetch=FetchType.EAGER)
	@OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
	private UserSettings userSettings;

	/**
	 * 	@ ElementCollection(fetch=FetchType.EAGER)
	 * String type chosen for speedup Datastore operations.
	 */
	private Collection<String> authorities;
	
	/**
	 * It stores the last 5 signin addresses as a commadelimated list along with
	 * the TimeStamp of signin Time, in the Format:
	 * <IP_ADDRESS>=<TIMESTAMP>,<IP_ADDRESS>=<TIMESTAMP>,....up to 5 levels.
	 * This structure is selected to reduce the Datastore API calls, by avoiding 
	 * Arrays or Collections.
	 */
	private String signinHistory;
	
	private boolean accountNonExpired;
	private boolean accountNonLocked;
	private boolean credentialsNonExpired;
	private boolean verified;
	private boolean enabled;
	private String description;

	// ~ Constructors
	// =============================================================

	
	/**
	 * Default constructor to support for JPA.
	 */
	protected User() {
		super();
	}

	/**
	 * Construct the <code>User</code> with the details required by
	 * {@link org.acegisecurity.providers.dao.DaoAuthenticationProvider}.
	 * 
	 * @param username
	 *            the username presented to the
	 *            <code>DaoAuthenticationProvider</code>
	 * @param password
	 *            the password that should be presented to the
	 *            <code>DaoAuthenticationProvider</code>
	 * @param enabled
	 *            set to <code>true</code> if the user is enabled
	 * @param accountNonExpired
	 *            set to <code>true</code> if the account has not expired
	 * @param credentialsNonExpired
	 *            set to <code>true</code> if the credentials have not expired
	 * @param accountNonLocked
	 *            set to <code>true</code> if the account is not locked
	 * @param authorities
	 *            the authorities that should be granted to the caller if they
	 *            presented the correct username and password and the user is
	 *            enabled
	 * 
	 * @throws IllegalArgumentException
	 *             if a <code>null</code> value was passed either as a parameter
	 *             or as an element in the <code>GrantedAuthority[]</code> array
	 */
	public User(Key key, String firstName, String lastName,
			String middleName, String phoneNumber, Date birthDate, String username, String password,
			String email, UserSettings userSettings, boolean enabled, boolean verified,
			boolean accountNonExpired, boolean credentialsNonExpired,
			boolean accountNonLocked, GrantedAuthority[] authorities, String signinHistory, String description)
			throws IllegalArgumentException {
		
		if (((username == null) || "".equals(username)) || (password == null) || email == null || "".equals(email)) {
			throw new IllegalArgumentException(
					"Cannot pass null or empty values to constructor " + "Username: " + username +" PWD: " 
					+ ((password == null)? null: "[NOT NULL PROTECTED]" )+" email: "+ email);
		}
		
		this.key = key;
		
		this.firstName = firstName;
		this.lastName = lastName;
		this.middleName = middleName;
		this.phoneNumber = phoneNumber;
		this.birthDate = birthDate;

		this.username = username;
		this.email = email;
		this.password = password;
		this.userSettings = userSettings;
		this.enabled = enabled;
		this.verified = verified;
		this.accountNonExpired = accountNonExpired;
		this.credentialsNonExpired = credentialsNonExpired;
		this.accountNonLocked = accountNonLocked;
		setAuthoritiesAsGA(authorities);
		this.signinHistory = signinHistory;
		this.description = description;
	}

	// ~ Methods
	// ====================================================================

	public boolean equals(Object rhs) {
		if (!(rhs instanceof User) || (rhs == null)) {
			return false;
		}

		User user = (User) rhs;

		// We rely on constructor to guarantee any User has non-null and >0
		// authorities
		if (user.getAuthorities().length != this.getAuthorities().length) {
			return false;
		}

		for (int i = 0; i < this.getAuthorities().length; i++) {
			if (!this.getAuthorities()[i].equals(user.getAuthorities()[i])) {
				return false;
			}
		}

		// We rely on constructor to guarantee non-null username and password
		return (this.getPassword().equals(user.getPassword())
				&& this.getUsername().equals(user.getUsername())
				&& (this.isAccountNonExpired() == user.isAccountNonExpired())
				&& (this.isAccountNonLocked() == user.isAccountNonLocked())
				&& (this.isCredentialsNonExpired() == user
						.isCredentialsNonExpired()) && (this.isEnabled() == user
				.isEnabled()));
	}

	public GrantedAuthority[] getAuthorities() {
		int i=0;
		List<GrantedAuthority> gas = new ArrayList<GrantedAuthority>(); 
		for(String s: this.authorities) {
			i++;
			gas.add(new GrantedAuthorityImplVO(s));
		}
		GrantedAuthority[] gaArray = new GrantedAuthorityImplVO[i];
		return (GrantedAuthority[]) gas.toArray(gaArray);
	}

	public String getPassword() {
		return password;
	}

	public String getUsername() {
		return username;
	}

	public int hashCode() {
		int code = 9792;

		if (this.getAuthorities() != null) {
			for (int i = 0; i < this.getAuthorities().length; i++) {
				code = code * (this.getAuthorities()[i].hashCode() % 7);
			}
		}

		if (this.getPassword() != null) {
			code = code * (this.getPassword().hashCode() % 7);
		}

		if (this.getUsername() != null) {
			code = code * (this.getUsername().hashCode() % 7);
		}

		if (this.isAccountNonExpired()) {
			code = code * -2;
		}

		if (this.isAccountNonLocked()) {
			code = code * -3;
		}

		if (this.isCredentialsNonExpired()) {
			code = code * -5;
		}

		if (this.isEnabled()) {
			code = code * -7;
		}

		return code;
	}

	public boolean isAccountNonExpired() {
		return accountNonExpired;
	}

	public boolean isAccountNonLocked() {
		return this.accountNonLocked;
	}

	public boolean isCredentialsNonExpired() {
		return credentialsNonExpired;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setAuthorities(Collection<String> authorities) {
		this.authorities = authorities;
	}
	
	public void setAuthoritiesAsGA(GrantedAuthority[] authorities) {
		if (authorities == null) {
			throw new IllegalArgumentException(
					"Cannot pass a null GrantedAuthority array");
		}

		for (int i = 0; i < authorities.length; i++) {
			if (authorities[i] == null) {
				throw new IllegalArgumentException(
						"Granted authority element "
								+ i
								+ " is null - GrantedAuthority[] cannot contain any null elements");
			}

		}
		Set<String> s = new HashSet<String>();
		for(GrantedAuthority ga:authorities){
			s.add(ga.toString());
		}

		this.authorities = s;
	}

	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(super.toString()).append(": ");
		sb.append("Username: ").append(this.username).append("; ");
		sb.append("Password: [PROTECTED]; ");
		sb.append("Enabled: ").append(this.enabled).append("; ");
		sb.append("AccountNonExpired: ").append(this.accountNonExpired).append(
				"; ");
		sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired)
				.append("; ");
		sb.append("AccountNonLocked: ").append(this.accountNonLocked).append(
				"; ");

		if (this.getAuthorities() != null) {
			sb.append("Granted Authorities: ");

			for (int i = 0; i < this.getAuthorities().length; i++) {
				if (i > 0) {
					sb.append(", ");
				}

				sb.append(this.getAuthorities()[i].toString());
			}
		} else {
			sb.append("Not granted any authorities");
		}

		return sb.toString();
	}

	public void setUserSettings(UserSettings userSettings) {
		this.userSettings = userSettings;
	}

	public UserSettings getUserSettings() {
		return userSettings;
	}
	
	private static final long serialVersionUID = 1L;

	public void setPassword(String password) {
		this.password = password;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public String getMiddleName() {
		return middleName;
	}

	public String getPhoneNumber() {
		return phoneNumber;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public void setMiddleName(String middleName) {
		this.middleName = middleName;
	}

	public void setPhoneNumber(String phoneNumber) {
		this.phoneNumber = phoneNumber;
	}

	public void setAccountNonExpired(boolean accountNonExpired) {
		this.accountNonExpired = accountNonExpired;
	}

	public void setAccountNonLocked(boolean accountNonLocked) {
		this.accountNonLocked = accountNonLocked;
	}

	public void setCredentialsNonExpired(boolean credentialsNonExpired) {
		this.credentialsNonExpired = credentialsNonExpired;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}


	public void setEmail(String email) {
		this.email = email;
	}

	public String getEmail() {
		return email;
	}

	public void setKey(Key id) {
		this.key = id;
	}

	public Key getKey() {
		return key;
	}

	public String getKeyAsStringId() {
		return KeyFactory.keyToString(key);
		
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getDescription() {
		return description;
	}

	/**
	 * @param birthDate the birthDate to set
	 */
	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

	/**
	 * @return the birthDate
	 */
	public Date getBirthDate() {
		return birthDate;
	}

	/**
	 * @param verified the verified to set
	 */
	public void setVerified(boolean verified) {
		this.verified = verified;
	}

	/**
	 * @return the verified
	 */
	public boolean isVerified() {
		return verified;
	}

	/**
	 * @param signinHistory
	 *            the signinHistory to set
	 */
	public void setSigninHistory(String signinHistory) {
		this.signinHistory = signinHistory;
	}

	/**
	 * @return the signinHistory
	 */
	public String getSigninHistory() {
		return signinHistory;
	}
	


}
